/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * $Id: MaCIData.hpp,v 1.16 2009-05-13 07:18:15 amaula Exp $
 *
 * \file
 * \brief MaCI - Baseclass for MaCI Data Encoder/Decoders.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#ifndef _MACI_MACIDATA_HPP_
#define _MACI_MACIDATA_HPP_
#include "binbag.h"
#include "owndebug.h"
#include <stdio.h>
#include <assert.h>




/** Macro for adding Dataelements.
 *
 * This macro does a few things:
 * - Adds a new binary in the 'iBinBagContainer', with 'KEY', and 'DATA', and sizeof('TYPE')
 * - Stores the pointer from the BinBag to 'PTR'
 * - Marks the variable 'iLastElementType' to be 'KEY'
 *
 * \note The \p iLastElementType is not defined in this base class, so to
 * be able to use this macro, you will have to define it yourself.
 * 
 * @param[in] TYPE              Type of the element, such as TAccess
 * @param[in] KEY               Key of element, such as KTypeAccess
 * @param[in] PTR               Structure pointer to element, such as iAccessPtr
 * @param[in] DATA              Datasource variable (pointer will be taken)
 */
#define ADDELEMENT(TYPE, KEY, PTR, DATA) const gim::binbag::TBinBlob *_b__ = \
                                         iBinBagContainer->AddBinary(KEY, (const char *)&DATA, sizeof(TYPE)); \
                                         PTR = reinterpret_cast<const TYPE *>(_b__->data); \
                                         SetLastElementType(KEY);

namespace MaCI {
    
  /** MaCI Data encoder/decoder baseclass.
   *
   * This is a Baseclass for implementing MaCI data
   * encoders/decoders. It contains methods that should be common to
   * all encoders/decoders to allow users to use them in a unified
   * fashion. Because MaCI is an open architecture, we cannot force
   * you to derive your Data encoder/decoder from this - but we won't
   * even try. If you want to implement your Server / Client
   * communication using some other format, you are free to do so; AS
   * LONG AS THE INTERFACE HAS A CLIENT AND CORRESPONDING SERVER PART
   * IMPLEMENTED!
   */
  class CMaCIData
  {
  public:

    /** Constructor.
     *
     * Constructor accepts a BinBag container pointer as parameter.
     * if the Container is set here, user may call the EncodeTo() and 
     * DecodeFrom() functions with NULL paramer with same result.
     */
    CMaCIData(gim::binbag::CBinBag *aBinBagContainer = NULL)
      : iBinBagContainer(aBinBagContainer),
        iInternalContainer(false),
        iReadOnlyContainer(false),
        iLastContainerElementType(-1) {
      dPrint(15,"CMaCIData %p constructed", this);
    }


    /** Destructor.
     *
     * Cleans up Internal BinBag if such exists.
     */
    virtual ~CMaCIData() {
      DestroyInternalBinBag();
      dPrint(15,"CMaCIData %p destructed", this);
    }


    /** Create Copy of MaCIData instance.
     *
     * This functions copies the MaCIData instance by:
     * 0. Reset current container by calling Reset()
     * 1. Calling CreateInternalBinBag() with the BinBag pointer from
     *    the supplied source object.
     * 2. Calling the DecodeFrom() function with NULL parameter to
     *    carry out the decoding process again.
     *
     * @param[in] aData         Pointer to CMaCIData complian class to duplicate.
     * @return                  The functions returns directly
     *                          the returnvalue of DecodeFrom function.
     */
    virtual bool Copy(const CMaCIData &aData) {
      // Reset container.
      Reset();

      // Copy the binbag by creating internal binbag and assigning data
      CreateInternalBinBag(aData.iBinBagContainer);
      
      // Regenerate pointers by running the DecodeFrom(NULL)
      return DecodeFrom(NULL);
    }
      

    /** Decode Data from given BinBag container.
     * 
     * This function should be implemented as follows:
     * It decodes the given BinBag, and assigns Data class
     * pointers to all correctly defined elements. It should
     * also check that the order and layout of elements is
     * sane; for example; if the BinBag contains a DistanceHeader,
     * it must contain required count of Distances too. This is 
     * ofcourse for the interface designer to decide.
     *
     * @param[in] aBinBag       Pointer to binbag::CBinBag instance
     *                          to Decode. If this parameter
     *                          is left as NULL, the BinBag 
     *                          specified by constructor will be used.
     *                          If the class was constructed with
     *                          no BinBag pointer, this
     *                          function call will fail.
     * @return                  true - when the BinBag was
     *                          correctly formed for the 
     *                          datatype at hand.
     *                          false - if the order / format
     *                          of the data is invalid.
     * 
     */
    virtual bool DecodeFrom(gim::binbag::CBinBag *aBinBag = NULL) = 0;


    /** Encode Data to given BinBag container.
     *
     * Default implementation of this function goes as follows:
     * 1. Call Reset() for this class.
     * 2. Store the provided BinBag pointer to classes internal
     *    storage. (Unless specified as NULL)
     * 3. Clear the current BinBag container.
     *
     * If you choose to override the default implementation,
     * your implementation should work as follows:
     * 1. Prepare the Data container to accept new dataelements
     *    by clearing it, and its backend to their default states.
     * \note The real encoding takes place on the first call
     * to any 'Set...' function.
     * 
     * @param[in] aBinBag       Pointer to binbag::CBinBag instance
     *                          which should be used as a
     *                          backend. If this parameter
     *                          is left as NULL, the BinBag 
     *                          specified by constructor will be used.
     *                          If the class was constructed with
     *                          no BinBag pointer, this
     *                          function call will fail.
     * @return                  true - when the BinBag was
     *                          accepted and Encoding may begin.
     *                          false - NULL BinBag provided,
     *                          or any fatal reason specified
     *                          by the implementing encoder/decoder.
     */
    virtual bool EncodeTo(gim::binbag::CBinBag *aBinBag = NULL) {
      // Call reset to clean up.
      Reset();
      
      if (aBinBag) {
        // Attempt to destroy just in case
        DestroyInternalBinBag();

        // Assign the aBinBag container and clean it up too
        iBinBagContainer = aBinBag;
        iBinBagContainer->Clear();
        return true;

      } else if (iInternalContainer) {
        // Clear the internal container.
        iBinBagContainer->Clear();
        return true;
        
      } else {
        // No internal container, and specified BinBag was NULL.
        return false;
      }
    }


    /** Reset the Data container.
     *
     * Calling this function should reset the Datacontainer to its
     * initial state, allowing it to decode/encode a new dataframe.
     *
     * All Data implementations must override this method to provide
     * user applications mean to clear the container. The base class
     * implementation only clear the underlying BinBag instance (if
     * set)
     */
    virtual void Reset(void) {
      if (iBinBagContainer) iBinBagContainer->Clear();
      SetReadOnlyContainer(false);
      SetLastElementType(-1);
    }


    /** Print out Datacontainer contents through the dPrint
     *  facility.
     *
     * This function should be implemented as follows by the
     * deriving class:
     * This function is for debugging purposes. It dumps the
     * contents of the current BinBagcontainer by traversing through
     * it, and printing each elements contents.
     *
     * @param[in] level         dPrint level to use when printing
     *                          element contents.
     */
    virtual void Print(const int level) const = 0;


    /** Return a constant pointer to used BinBag container.
     *
     */
    const gim::binbag::CBinBag *GetBinBagContainerPtr(void) const {
      return iBinBagContainer;
    }

    
    /** Initialize the class with internal BinBag instance.
     *
     * Calling this function will create an internal BinBag
     * instance. This instance is owned and handled by this class.
     * Using 'EncodeTo()' or 'DecodeFrom()' with Non-NULL parameter
     * will automatically destroy this internal BinBag instance.
     * The class will also free the Internal BinBag if it
     * is still in use when destructor is called.
     *
     * @param[in] aBinBag       Create the internal binbag duplicating
     *                          this BinBag pointer.
     * @return                  'true'
     */
    bool CreateInternalBinBag(const gim::binbag::CBinBag *aBinBag = NULL) {
      if (iInternalContainer) {
        // Already have a container?
        if (aBinBag) {
          // Also got a source BinBag -> Copy contents
          iBinBagContainer->Copy(*aBinBag);

        } else {
          // No source -> Just clear
          iBinBagContainer->Clear();

        }
      } else {
        // Construct new based on parameter.
        if (aBinBag) {
          iBinBagContainer = new gim::binbag::CBinBag(*aBinBag);

        } else {
          iBinBagContainer = new gim::binbag::CBinBag;

        }
        iInternalContainer = true;
      }

      return true;
    }


    /** Destroy internal BinBag instance.
     *
     * This function will free the previously created Internal BinBag
     * instance.
     *
     * \see CreateInternalBinBag() 
     *
     * @return                  'true'.
     */
    bool DestroyInternalBinBag(void) {
      if (iInternalContainer) {
        delete iBinBagContainer;
        iBinBagContainer = NULL;
        iInternalContainer = false;
      }
      return true;
    }

    
    /** Is the Decoder/Encoder using an internal BinBag instance?
     * 
     * @return                  True if the Internal BinBag is being used.
     *                          False if the Internal Binbag is not used.
     */
    inline bool IsInternalBinBag(void) const {
      return iInternalContainer;
    }


    /** Returns whether this container is marked as read-only.
     *
     * Returns whether this container has been marked as
     * read-only. Usually container is marked read-only when it is
     * decoded from an external BinBag. Read-only state is required to
     * keep the pointers intact.
     *
     * @return                  'true' if the container is read-only, otherwise
     *                          'false.
     */
    inline bool IsReadOnlyContainer(void) const {
      return iReadOnlyContainer;
    }

  protected: // Functions

    /** Change the read-only state.
     * 
     * This function alters the read-only status of the container.
     * 
     * @param[in] aReadOnly     If set to 'true' the container is marked
     *                          as read-only, if 'false' container is read-write.
     */
    inline void SetReadOnlyContainer(const bool aReadOnly = true) {
      iReadOnlyContainer = aReadOnly;
    }


    /** Set pointer to current binbag instance.
     *
     * This function updates the currently used binbag::CBinBag instance of
     * the class.
     * 
     * This function just updates the value of protected member
     *
     * @param[in] aBB           Pointer to valid binbag::CBinBag container, or NULL.
     */
    inline void SetBinBagContainerPtr(gim::binbag::CBinBag *aBB) {
      iBinBagContainer = aBB;
    }


    /** Set the last handled element type.
     *
     * This function just stores one integer. It can be used in
     * decoding/encoding process to track the sequence of
     * decoded/encoded entities.
     *
     * This function just updates the value of protected member
     * 'iLastContainerElementType'.
     *
     * @param[in] aElementType  Element type number
     */
    inline void SetLastElementType(const int aElementType) {
      if (iLastContainerElementType != aElementType)
        iLastContainerElementType = aElementType;
    }


    /** Get the value of last element type stored.
     *
     * @return                  Last element type stored. 
     *                          If not ever set, returns -1
     */
    inline int GetLastElementType(void) const {
      return iLastContainerElementType;
    }

  protected: // Members
    
    gim::binbag::CBinBag *iBinBagContainer; ///< Pointer to BinBag backend.
    bool iInternalContainer; ///< Whether the Container is internal or not.
    bool iReadOnlyContainer; ///< Is this container Read-Only?
    int iLastContainerElementType; ///< Stored last element type.


  private: // Functions
    CMaCIData(const CMaCIData &) 
      : iBinBagContainer(),
        iInternalContainer(false),
        iReadOnlyContainer(false),
        iLastContainerElementType(-1) {}
    CMaCIData &operator=(const CMaCIData &) { return *this; }
  };
}
#endif
